﻿
package extremefx.i18n {
	import extremefx.tools.getFunctionByName;	
	import extremefx.i18n.ICustomFormatter;
	import extremefx.i18n.IFormatProvider;
	import extremefx.i18n.IFormattable;	

	/**
	 * @author Marcelo Volmaro
	 */
	public final class XString {
		private static const _dateStr:Function = getFunctionByName("extremefx.i18n.XDate", "toString");
		private static const _numberStr:Function = getFunctionByName("extremefx.i18n.XNumber", "toString");
		
		private static const _icprov:DummyIFP = new DummyIFP();
		private static const _cfre:RegExp = /{(\d+),?(-?\d+)?:?([^\r\{}]+)?}/g;
		private static const _core:RegExp = /{([\w\.\[\]]+),?(-?\d+)?:?([^\r\{}]+)?}/g;
		
		
		/**
		 * The syntax of a format item is {index[,alignment][:formatString]}, 
		 * which specifies a mandatory index, the optional length and alignment 
		 * of the formatted text, and an optional string of format specifier 
		 * characters that govern how the value of the corresponding object is formatted.
		 * @param args: [IFormatProvider, String to be formatted | String to be formatted], arguments
		 */
		public static function format(...args):String{
			if (!(args[0] is IFormatProvider)){
				args.unshift(null);
			}
			
			return _toFormattedString.apply(XString, args);
		}
		
		/**
		 * The syntax of a format item is {property[,alignment][:formatString]}, 
		 * which specifies a mandatory property, the optional length and alignment 
		 * of the formatted text, and an optional string of format specifier 
		 * characters that govern how the value of the corresponding object is formatted.
		 * @param args: [IFormatProvider, String to be formatted | String to be formatted], arguments
		 */
		public static function formatWith(...args):String{
			if (!(args[0] is IFormatProvider)){
				args.unshift(null);
			}
			
			var	str:String = args[1],
			values:Array = [args[0], ""],
			idx:uint = 2,
			source:Object = args[2],
			getObjProp:Function = function(pSrc:Object, pProp:String):Object {
				var parts:Array = pProp.split(".");
				for each(var part:String in parts){
					pSrc = pSrc[part];
				}
				
				return pSrc;
			},
			
			evaluator:Function = function(pMatch:String, ...args):String {
				var prop:String = args[0],
				aFmt:String = args[2],
				pad:int = parseInt(args[1]) || 0,
				ret:String = "{"+(idx-2);
				values[idx++] = prop == "0" ? source : getObjProp(source, prop);
				
				if (pad) ret += ","+pad;
				if (aFmt && aFmt != "") ret +":"+aFmt;
				return ret +"}";
			};
			
			values[1] = str.replace(_core, evaluator);
			return _toFormattedString.apply(XString, values);
		}
		
		internal static function _toFormattedString(pFmtProv:IFormatProvider, pStr:String, ...args):String{
			if (pStr == null || args == null) throw new ArgumentError();
			
			var fmtArgs:Object,
			cArg:*,
			aFmt:String,
			pad:int,
			formatter:ICustomFormatter = pFmtProv == null ? null : (pFmtProv.getFormat(_icprov) as ICustomFormatter),
			str:String,
			retString:String = pStr;
			
			while ((fmtArgs = _cfre.exec(pStr)) != null) {
				cArg = args[fmtArgs[1]];

				aFmt = fmtArgs[3] || "";
				pad = parseInt(fmtArgs[2]) || 0;
				
				if (formatter){
					str = formatter.format(aFmt, cArg, pFmtProv);
				
				} else if (cArg is IFormattable){
					str = IFormattable(cArg).toString(aFmt, pFmtProv);
				
				} else {
					if (cArg is Date && _dateStr != null) {
						str = _dateStr(cArg, aFmt);
						
					} else if (cArg is Number && _numberStr != null) {
						str = _numberStr(cArg, aFmt);
						
					} else {
						str = cArg.toString();
					}
				}
				
				if (pad != 0){
					var diff:int = Math.abs(pad) - str.length;
					if (0 < diff){
						if (pad < 0){
							while (diff--) str += " ";
							
						} else {
							var ret:String = "";
							while (diff--) ret += " ";
							str = ret + str;
						}
					}
				}
				
				retString = retString.replace(fmtArgs[0], str);
			}
			
			return retString;
		}		 
	}
}